Responsive UI in Flutter
Responsive UI in Flutter means designing an application so that its interface automatically adjusts to the available space. A responsive application should remain usable and visually organized across mobile phones, tablets, foldables, desktops, and web browsers. Flutter describes responsive design as fitting the UI into the available space, while adaptive design focuses on making that UI usable within the available space. In real applications, both approaches are commonly used together. :contentReference[oaicite:0]{index=0}
1. What Is Responsive UI?
A responsive UI changes the arrangement, sizing, spacing, and positioning of widgets according to the available screen or window size.
For example, a product page may display:
- One product column on a small mobile screen.
- Two or three columns on a tablet.
- Several appropriately sized columns on a desktop.
The important point is that responsive design does not mean creating completely separate applications for every device. Instead, the application uses the same data and reusable widgets while adapting their presentation.
2. Why Responsive UI Is Important
- Flutter applications can run across many screen sizes and platforms.
- Users may resize desktop or web application windows.
- Tablets can run applications in multi-window mode.
- Foldable devices can provide changing amounts of usable space.
- Different devices support touch, mouse, trackpad, and keyboard input.
- Large screens require different information arrangements from small screens.
- Responsive layouts reduce overflow and improve usability.
Flutter's adaptive documentation emphasizes supporting different screen sizes, orientations, platforms, and input methods from a single codebase. :contentReference[oaicite:1]{index=1}
3. Responsive vs Adaptive UI
| Responsive UI |
Adaptive UI |
| Fits UI elements into available space. |
Chooses an appropriate UI structure for the available space. |
| Changes sizes, spacing, columns, and positions. |
Can change navigation and interaction patterns. |
| Example: changing a grid from 4 columns to 2 columns. |
Example: changing NavigationBar to NavigationRail. |
| Primarily concerns layout. |
Primarily concerns usability and presentation. |
4. Responsive Design Based on Available Space
A common mistake is assuming that a particular physical device always provides a particular amount of space. A tablet can run an application in a narrow split-screen window, while a desktop application can be resized to a small window.
For this reason, Flutter recommends making layout decisions based on the application's available window size rather than simply checking whether the device is a phone, tablet, or desktop. :contentReference[oaicite:2]{index=2}
Example
final width = MediaQuery.sizeOf(context).width;
if (width < 600) {
return const MobileLayout();
}
return const LargeLayout();
5. MediaQuery in Responsive UI
MediaQuery provides information about the current application environment. For responsive layouts, MediaQuery.sizeOf(context) is particularly useful for obtaining the current application window size.
Widget build(BuildContext context) {
final size = MediaQuery.sizeOf(context);
return Column(
children: [
Text('Width: ${size.width}'),
Text('Height: ${size.height}'),
],
);
}
MediaQuery.sizeOf returns the application's window size in logical pixels. Flutter recommends it when a layout decision depends on the overall application window. :contentReference[oaicite:3]{index=3}
6. Why Use MediaQuery.sizeOf Instead of MediaQuery.of?
Older Flutter code commonly uses MediaQuery.of(context).size. Modern Flutter guidance recommends specialized MediaQuery accessors such as MediaQuery.sizeOf(context) when only the size is needed.
final size = MediaQuery.sizeOf(context);
This more specifically expresses that the widget depends on the MediaQuery size and can avoid unnecessary rebuild dependencies on unrelated MediaQuery properties. :contentReference[oaicite:4]{index=4}
7. LayoutBuilder for Responsive Components
LayoutBuilder is useful when a widget should respond to the space provided by its parent rather than the entire application window.
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 400) {
return const CompactCard();
}
return const WideCard();
},
)
LayoutBuilder provides a BoxConstraints object containing the minimum and maximum width and height available to the widget. :contentReference[oaicite:5]{index=5}
8. MediaQuery vs LayoutBuilder
| MediaQuery.sizeOf |
LayoutBuilder |
| Measures the application window. |
Measures constraints from the parent. |
| Useful for page-level responsive decisions. |
Useful for component-level responsive decisions. |
| Returns a Size. |
Provides BoxConstraints. |
| Useful for navigation changes. |
Useful for cards, panels, and reusable widgets. |
Flutter's general adaptive approach recommends using MediaQuery.sizeOf for whole-window measurements and LayoutBuilder for local layout constraints. :contentReference[oaicite:6]{index=6}
9. Responsive Breakpoints
A breakpoint is a width at which the application changes its layout because the current structure no longer fits comfortably.
const double mobileBreakpoint = 600;
const double tabletBreakpoint = 900;
const double desktopBreakpoint = 1200;
These values are examples. Breakpoints should be selected based on the requirements of your layout, not simply because a device has been classified as a phone or tablet.
Responsive Breakpoint Example
Widget buildResponsiveLayout(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
if (width < 600) {
return const MobileLayout();
}
if (width < 1200) {
return const TabletLayout();
}
return const DesktopLayout();
}
10. Flutter's Abstract, Measure, and Branch Approach
Flutter's general approach to adaptive applications can be summarized into three steps:
- Abstract: Identify data and widgets that can be shared between layouts.
- Measure: Determine how much space is available using
MediaQuery or LayoutBuilder.
- Branch: Select the appropriate layout based on the available space.
This approach makes responsive code easier to maintain because the application does not duplicate business logic for every screen size. :contentReference[oaicite:7]{index=7}
11. Responsive Row and Column
A common responsive technique is changing a horizontal layout into a vertical layout when space becomes limited.
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 700) {
return const Column(
children: [
ProductImage(),
ProductDetails(),
],
);
}
return const Row(
children: [
Expanded(
child: ProductImage(),
),
SizedBox(width: 24),
Expanded(
child: ProductDetails(),
),
],
);
},
)
12. Using Expanded
Expanded allows a widget to occupy available space inside a Row, Column, or another Flex layout.
Row(
children: [
Expanded(
flex: 2,
child: Container(
height: 100,
child: const Center(
child: Text('Main Content'),
),
),
),
Expanded(
flex: 1,
child: Container(
height: 100,
child: const Center(
child: Text('Side Content'),
),
),
),
],
)
The flex values control how the available space is distributed.
13. Using Flexible
Flexible allows a child to use available space without requiring it to fill all remaining space.
Row(
children: [
Flexible(
child: Text(
'This text can shrink or wrap when the available width becomes smaller.',
),
),
const Icon(Icons.info),
],
)
This is particularly useful for text-heavy rows where a fixed width could otherwise cause overflow.
14. Responsive Navigation
Navigation is an important part of responsive UI. A small window may work well with bottom navigation, while a wider window can use a navigation rail or sidebar.
Widget buildNavigation(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
if (width < 600) {
return const NavigationBar(
destinations: [
NavigationDestination(
icon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.search),
label: 'Search',
),
NavigationDestination(
icon: Icon(Icons.person),
label: 'Profile',
),
],
);
}
return const NavigationRail(
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.search),
label: Text('Search'),
),
NavigationRailDestination(
icon: Icon(Icons.person),
label: Text('Profile'),
),
],
selectedIndex: 0,
);
}
Flutter's adaptive guidance uses navigation as an example of how the same destinations can be presented differently depending on available space. :contentReference[oaicite:8]{index=8}
15. Responsive Sidebar
Large screens can provide enough horizontal space for a persistent sidebar.
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 700) {
return const MobileHomePage();
}
return Row(
children: [
const SizedBox(
width: 250,
child: Sidebar(),
),
const VerticalDivider(width: 1),
const Expanded(
child: DetailsPage(),
),
],
);
},
)
Flutter's adaptive layout tutorial demonstrates this general pattern, using a sidebar and details area on wider screens and navigation-based presentation on smaller screens. :contentReference[oaicite:9]{index=9}
16. Responsive GridView
Grid layouts should adapt to the available width instead of relying on a fixed number of columns for a specific physical device.
GridView.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 280,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 1.2,
),
itemCount: products.length,
itemBuilder: (context, index) {
return ProductCard(
product: products[index],
);
},
)
Using maxCrossAxisExtent allows the number of columns to respond naturally to the available window width. Flutter's large-screen guidance recommends basing grid behavior on available window size rather than physical device categories. :contentReference[oaicite:10]{index=10}
17. Responsive Grid Using LayoutBuilder
LayoutBuilder(
builder: (context, constraints) {
int columns;
if (constraints.maxWidth < 600) {
columns = 1;
} else if (constraints.maxWidth < 1000) {
columns = 2;
} else {
columns = 4;
}
return GridView.count(
crossAxisCount: columns,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
children: const [
ProductCard(),
ProductCard(),
ProductCard(),
ProductCard(),
],
);
},
)
18. Responsive Forms
Forms should remain readable and easy to use on both small and large screens.
Mobile Layout
Column(
children: [
TextField(
decoration: const InputDecoration(
labelText: 'First Name',
),
),
const SizedBox(height: 16),
TextField(
decoration: const InputDecoration(
labelText: 'Last Name',
),
),
],
)
Large Layout
Row(
children: [
Expanded(
child: TextField(
decoration: const InputDecoration(
labelText: 'First Name',
),
),
),
const SizedBox(width: 16),
Expanded(
child: TextField(
decoration: const InputDecoration(
labelText: 'Last Name',
),
),
),
],
)
19. Limit Form Width on Large Screens
Forms that stretch across an entire desktop window can become uncomfortable to use. A maximum width keeps the content focused.
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 600,
),
child: const RegistrationForm(),
),
)
Flutter recommends avoiding layouts that simply consume all available horizontal space on large displays. :contentReference[oaicite:11]{index=11}
20. ConstrainedBox for Responsive Content
ConstrainedBox can provide a maximum width for content while still allowing the application to expand around it.
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 1000,
),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: const [
Text('Main Content'),
],
),
),
),
)
21. Responsive Padding
Padding can be adjusted based on available space.
final width = MediaQuery.sizeOf(context).width;
final padding = width < 600
? 16.0
: width < 1000
? 24.0
: 40.0;
return Padding(
padding: EdgeInsets.symmetric(
horizontal: padding,
),
child: const PageContent(),
);
Use a small and consistent spacing system rather than creating many arbitrary breakpoints.
22. Responsive Text
Text should be readable and should have enough room to wrap naturally.
Expanded(
child: Text(
'This is a responsive text example that can wrap when the available width becomes smaller.',
softWrap: true,
),
)
Prefer theme-based typography rather than manually defining unrelated font sizes for every screen.
Using Theme Typography
Text(
'Welcome to Flutter',
style: Theme.of(context).textTheme.headlineMedium,
)
23. Responsive Cards
Create a reusable card and allow the parent layout to determine how many cards are displayed in a row.
class ProductCard extends StatelessWidget {
final String name;
final double price;
const ProductCard({
super.key,
required this.name,
required this.price,
});
@override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(
Icons.shopping_bag,
size: 48,
),
const SizedBox(height: 12),
Text(
name,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Text('₹$price'),
],
),
),
);
}
}
24. Responsive Images
Images should maintain their proportions when their container changes size.
AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(
imageUrl,
fit: BoxFit.cover,
),
)
AspectRatio helps maintain a predictable image shape across different layouts.
25. Responsive Wrap
Wrap is useful for buttons, filters, tags, and chips that need to move to another line when horizontal space becomes limited.
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton(
onPressed: () {},
child: const Text('All'),
),
ElevatedButton(
onPressed: () {},
child: const Text('Popular'),
),
ElevatedButton(
onPressed: () {},
child: const Text('New'),
),
ElevatedButton(
onPressed: () {},
child: const Text('Discount'),
),
],
)
26. Responsive Dashboard
A dashboard can use the same statistics and widgets while changing their arrangement.
LayoutBuilder(
builder: (context, constraints) {
final width = constraints.maxWidth;
final columns = width < 600
? 1
: width < 1000
? 2
: 4;
return GridView.count(
crossAxisCount: columns,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: const [
StatCard(
title: 'Users',
value: '12,450',
),
StatCard(
title: 'Orders',
value: '3,240',
),
StatCard(
title: 'Revenue',
value: '₹8,45,000',
),
StatCard(
title: 'Pending',
value: '128',
),
],
);
},
)
27. Responsive Login UI
LayoutBuilder(
builder: (context, constraints) {
final isWide = constraints.maxWidth >= 800;
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 900,
),
child: Padding(
padding: const EdgeInsets.all(24),
child: isWide
? Row(
children: const [
Expanded(
child: LoginIllustration(),
),
Expanded(
child: LoginForm(),
),
],
)
: const LoginForm(),
),
),
);
},
)
On a narrow screen, the form can occupy the available width. On a wider screen, the illustration and form can appear side by side.
28. Responsive List-Detail Layout
List-detail interfaces are useful for contacts, email, products, messages, and admin applications.
Large Screen
Row(
children: [
SizedBox(
width: 300,
child: ContactList(),
),
const VerticalDivider(width: 1),
const Expanded(
child: ContactDetails(),
),
],
)
Small Screen
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const ContactDetails(),
),
);
Flutter's adaptive layout tutorial demonstrates the general concept of displaying related content side-by-side on larger screens and using navigation on smaller screens. :contentReference[oaicite:12]{index=12}
29. SafeArea in Responsive UI
SafeArea prevents important content from being obscured by display cutouts, notches, rounded corners, status bars, and other system UI.
Scaffold(
body: SafeArea(
child: Column(
children: [
const Text('Dashboard'),
Expanded(
child: ListView(
children: const [
ListTile(
title: Text('Item 1'),
),
ListTile(
title: Text('Item 2'),
),
],
),
),
],
),
),
)
Flutter recommends using SafeArea around content that should avoid these intrusions; wrapping the body of a Scaffold is a common starting point. :contentReference[oaicite:13]{index=13}
30. Do Not Check Phone or Tablet Type
Bad Approach
if (isTablet) {
return const TabletLayout();
}
return const PhoneLayout();
Better Approach
final width = MediaQuery.sizeOf(context).width;
if (width >= 600) {
return const LargeLayout();
}
return const SmallLayout();
Physical device type does not necessarily tell you how much space the application currently has. Flutter can run in resizable windows, multi-window environments, web browsers, and other configurations. :contentReference[oaicite:14]{index=14}
31. Avoid Orientation-Only Layout Decisions
Do not assume that landscape automatically means tablet or desktop.
Less Recommended
if (MediaQuery.orientationOf(context) == Orientation.landscape) {
return const DesktopLayout();
}
Recommended
final width = MediaQuery.sizeOf(context).width;
if (width >= 900) {
return const WideLayout();
}
return const CompactLayout();
Flutter specifically recommends using available size through MediaQuery.sizeOf or LayoutBuilder instead of relying on orientation alone for major layout decisions. :contentReference[oaicite:15]{index=15}
32. Avoid Unnecessary Fixed Widths
Problematic Code
Container(
width: 700,
child: const Text('Responsive content'),
)
This can overflow on smaller screens.
Better Code
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
child: const Text('Responsive content'),
)
For large screens, combine flexible sizing with maximum-width constraints.
33. Use Maximum Width on Large Screens
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 1200,
),
child: const DashboardContent(),
),
)
This prevents the interface from becoming excessively stretched on wide monitors. Flutter's large-screen recommendations specifically address avoiding full-width text fields and boxes. :contentReference[oaicite:16]{index=16}
34. Support Different Input Devices
Responsive UI is not only about screen size. Desktop and web users may interact with the application using a mouse, trackpad, or keyboard, while mobile users primarily use touch.
- Provide comfortable touch targets.
- Support mouse interaction.
- Support keyboard navigation.
- Provide hover feedback where appropriate.
- Support trackpad scrolling.
- Do not make essential functionality dependent only on hover.
Flutter recommends supporting mice, trackpads, keyboard shortcuts, and keyboard navigation, while Material widgets provide many default interaction behaviors. :contentReference[oaicite:17]{index=17}
35. Hover Interaction Example
class HoverCard extends StatefulWidget {
const HoverCard({super.key});
@override
State createState() => _HoverCardState();
}
class _HoverCardState extends State {
bool isHovered = false;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) {
setState(() {
isHovered = true;
});
},
onExit: (_) {
setState(() {
isHovered = false;
});
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
blurRadius: isHovered ? 12 : 4,
),
],
),
child: const Text('Hover over me'),
),
);
}
}
36. Preserve Application State
A responsive application should not unnecessarily lose user data when its window changes size, rotates, or changes configuration.
Important state can include:
- Form values.
- Selected navigation item.
- Search text.
- Filters.
- Selected products.
- Scroll position.
- Expanded sections.
Flutter recommends retaining or restoring application state as the device rotates, the window changes size, or a foldable changes configuration. :contentReference[oaicite:18]{index=18}
37. Preserve List Scroll Position
PageStorageKey can help preserve scroll state for lists that maintain the same general layout.
ListView.builder(
key: const PageStorageKey('products'),
itemCount: products.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(products[index].name),
);
},
)
Flutter's adaptive best-practice guidance specifically recommends PageStorageKey for preserving suitable list state. :contentReference[oaicite:19]{index=19}
38. Do Not Lock Orientation Unnecessarily
Responsive applications should generally be able to handle different window sizes and shapes. Locking the application to one orientation can make adaptive behavior more difficult, particularly for large screens, multi-window environments, and foldables.
Flutter recommends avoiding unnecessary orientation locks and designing for different window sizes instead. :contentReference[oaicite:20]{index=20}
39. Responsive UI for Foldables
Foldable devices can change the available display area when they are folded or unfolded. Flutter's MediaQuery can also provide information related to display features such as folds or hinges.
final mediaQuery = MediaQuery.of(context);
final features = mediaQuery.displayFeatures;
For applications targeting foldables, avoid assuming that the application always occupies one fixed rectangular area. :contentReference[oaicite:21]{index=21}
40. Responsive UI for Desktop and Web
Desktop and web applications often provide significantly more horizontal space than mobile devices. However, simply stretching mobile content across the entire screen does not always create a good experience.
Useful desktop/web patterns include:
- Maximum content widths.
- Side navigation.
- NavigationRail.
- Multi-column dashboards.
- List-detail layouts.
- Responsive data tables.
- Keyboard shortcuts.
- Mouse hover feedback.
41. Responsive Data Table
Tables can become difficult to read on narrow screens. One solution is to switch from a table to cards or a list.
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 700) {
return const MobileOrderList();
}
return const DesktopOrderTable();
},
)
This is an example of adaptive information architecture: the data remains the same while the presentation changes to suit the available space.
42. Break Large Widgets into Smaller Widgets
Complex responsive screens are easier to maintain when divided into smaller widgets.
class DashboardPage extends StatelessWidget {
const DashboardPage({super.key});
@override
Widget build(BuildContext context) {
return const Column(
children: [
DashboardHeader(),
DashboardStats(),
DashboardChart(),
RecentOrders(),
],
);
}
}
Flutter recommends breaking complex widgets into smaller widgets. This can improve code organization and also make adaptive changes easier. Flutter notes that many small const widgets can also help rebuild performance. :contentReference[oaicite:22]{index=22}
43. Use const Widgets
const Padding(
padding: EdgeInsets.all(16),
child: Text('Welcome'),
)
Use const where widgets and their inputs are compile-time constants. This helps Flutter reuse constant widget instances and can reduce unnecessary work during rebuilds. :contentReference[oaicite:23]{index=23}
44. Separate Data from Responsive UI
Different screen layouts should share the same application data and business logic.
class Product {
final String name;
final double price;
const Product({
required this.name,
required this.price,
});
}
The same product list can then be displayed using different layouts.
ResponsiveLayout(
mobile: ProductList(products: products),
tablet: ProductGrid(products: products),
desktop: ProductGridWide(products: products),
)
This prevents duplication and keeps responsive behavior focused on presentation.
45. Reusable ResponsiveLayout Widget
class ResponsiveLayout extends StatelessWidget {
final Widget mobile;
final Widget tablet;
final Widget desktop;
const ResponsiveLayout({
super.key,
required this.mobile,
required this.tablet,
required this.desktop,
});
@override
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
if (width < 600) {
return mobile;
}
if (width < 1200) {
return tablet;
}
return desktop;
}
}
Usage
ResponsiveLayout(
mobile: const MobileDashboard(),
tablet: const TabletDashboard(),
desktop: const DesktopDashboard(),
)
46. Responsive Component with LayoutBuilder
class AdaptiveCard extends StatelessWidget {
const AdaptiveCard({super.key});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final compact = constraints.maxWidth < 300;
return Card(
child: Padding(
padding: EdgeInsets.all(
compact ? 12 : 20,
),
child: compact
? const Column(
children: [
Icon(Icons.star),
SizedBox(height: 8),
Text('Compact'),
],
)
: const Row(
children: [
Icon(Icons.star),
SizedBox(width: 12),
Text('Wide'),
],
),
),
);
},
);
}
}
47. Complete Responsive Home Screen Example
import 'package:flutter/material.dart';
void main() {
runApp(const ResponsiveApp());
}
class ResponsiveApp extends StatelessWidget {
const ResponsiveApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorSchemeSeed: Colors.blue,
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Responsive Flutter'),
),
body: SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 600) {
return const MobileHome();
}
if (constraints.maxWidth < 1000) {
return const TabletHome();
}
return const DesktopHome();
},
),
),
);
}
}
class MobileHome extends StatelessWidget {
const MobileHome({super.key});
@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(16),
children: const [
DashboardCard(title: 'Users'),
DashboardCard(title: 'Orders'),
DashboardCard(title: 'Revenue'),
],
);
}
}
class TabletHome extends StatelessWidget {
const TabletHome({super.key});
@override
Widget build(BuildContext context) {
return GridView.count(
padding: const EdgeInsets.all(24),
crossAxisCount: 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
children: const [
DashboardCard(title: 'Users'),
DashboardCard(title: 'Orders'),
DashboardCard(title: 'Revenue'),
DashboardCard(title: 'Products'),
],
);
}
}
class DesktopHome extends StatelessWidget {
const DesktopHome({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 1200,
),
child: Row(
children: const [
SizedBox(
width: 240,
child: Sidebar(),
),
VerticalDivider(width: 1),
Expanded(
child: DesktopDashboard(),
),
],
),
),
);
}
}
class Sidebar extends StatelessWidget {
const Sidebar({super.key});
@override
Widget build(BuildContext context) {
return ListView(
children: const [
ListTile(
leading: Icon(Icons.dashboard),
title: Text('Dashboard'),
),
ListTile(
leading: Icon(Icons.shopping_cart),
title: Text('Orders'),
),
ListTile(
leading: Icon(Icons.people),
title: Text('Users'),
),
],
);
}
}
class DesktopDashboard extends StatelessWidget {
const DesktopDashboard({super.key});
@override
Widget build(BuildContext context) {
return GridView.count(
padding: const EdgeInsets.all(24),
crossAxisCount: 2,
crossAxisSpacing: 20,
mainAxisSpacing: 20,
children: const [
DashboardCard(title: 'Users'),
DashboardCard(title: 'Orders'),
DashboardCard(title: 'Revenue'),
DashboardCard(title: 'Products'),
],
);
}
}
class DashboardCard extends StatelessWidget {
final String title;
const DashboardCard({
super.key,
required this.title,
});
@override
Widget build(BuildContext context) {
return Card(
child: Center(
child: Text(
title,
style: Theme.of(context).textTheme.titleLarge,
),
),
);
}
}
48. Recommended Responsive Project Structure
lib/
├── main.dart
├── core/
│ ├── theme/
│ │ └── app_theme.dart
│ ├── constants/
│ │ └── app_constants.dart
│ └── responsive/
│ └── responsive_layout.dart
├── models/
│ └── product.dart
├── widgets/
│ ├── app_container.dart
│ ├── product_card.dart
│ └── responsive_grid.dart
└── screens/
├── home/
│ ├── mobile_home.dart
│ ├── tablet_home.dart
│ └── desktop_home.dart
└── products/
├── product_list.dart
└── product_grid.dart
49. Responsive UI Testing
Responsive design should be tested at different window sizes instead of testing only one device.
Test Different Widths
- 320px approximately.
- 375px approximately.
- 430px approximately.
- 600px.
- 768px.
- 900px.
- 1024px.
- 1200px.
- 1440px and wider.
Also Test
- Portrait orientation.
- Landscape orientation.
- Resizable desktop windows.
- Browser resizing.
- Split-screen configurations.
- Foldable configurations where relevant.
- Large text settings.
- Keyboard navigation.
- Mouse and trackpad interaction.
- Touch interaction.
- Long text.
- Empty states.
- Loading states.
- Error states.
50. Common Responsive UI Mistakes
Mistake 1: Using Fixed Widths Everywhere
Large fixed widths can cause overflow on small screens.
Mistake 2: Checking Device Type
Do not use phone/tablet checks as the primary basis for layout decisions.
Mistake 3: Using Orientation as the Main Breakpoint
Orientation does not tell you exactly how much space your application has.
Mistake 4: Stretching Everything on Desktop
Use maximum widths and multi-column layouts rather than making every text field full width.
Mistake 5: Ignoring Keyboard and Mouse
Desktop and web users may need keyboard navigation, mouse interaction, and hover feedback.
Mistake 6: Losing User State
Changing the layout should not unnecessarily clear forms, selections, or scroll positions.
Mistake 7: Creating One Huge Widget
Large widgets make responsive changes harder to maintain.
Mistake 8: Testing Only One Screen
Always test different window sizes and configurations.
51. Responsive UI Best Practices Checklist
- Design for available window space.
- Use
MediaQuery.sizeOf for application-window measurements.
- Use
LayoutBuilder for local component constraints.
- Choose breakpoints based on layout requirements.
- Avoid phone/tablet device checks for layout decisions.
- Avoid orientation-only layout decisions.
- Use
Expanded and Flexible.
- Use
Wrap for flexible collections of controls.
- Use responsive grids.
- Use maximum content widths on large screens.
- Use
SafeArea where content needs protection.
- Support touch, mouse, trackpad, and keyboard input.
- Preserve important application and list state.
- Break large screens into smaller widgets.
- Use
const widgets wherever appropriate.
- Keep data and business logic independent from presentation.
- Do not lock orientation unnecessarily.
- Test multiple screen sizes and platforms.
52. Practical Responsive UI Development Workflow
- Identify the main content and user actions.
- Design the basic mobile-friendly layout.
- Identify where the layout becomes crowded.
- Create meaningful breakpoints.
- Use
MediaQuery.sizeOf or LayoutBuilder.
- Adapt rows, columns, grids, and navigation.
- Use maximum widths for large-screen content.
- Keep business logic and data shared across layouts.
- Preserve application state.
- Add appropriate mouse and keyboard interactions.
- Test different window sizes.
- Test different input methods.
- Refactor repeated responsive logic into reusable widgets.
53. Quick Revision Table
| Flutter Feature |
Purpose |
| MediaQuery.sizeOf |
Gets the current application window size. |
| LayoutBuilder |
Gets the constraints available to a widget. |
| Expanded |
Shares available space in Flex layouts. |
| Flexible |
Allows a child to flex without necessarily filling all available space. |
| Wrap |
Moves children to additional lines when necessary. |
| ConstrainedBox |
Limits content dimensions such as maximum width. |
| SafeArea |
Protects content from system UI and display cutouts. |
| GridView |
Creates responsive grid-based layouts. |
| NavigationBar |
Useful for compact navigation layouts. |
| NavigationRail |
Useful for wider layouts. |
| PageStorageKey |
Helps preserve list scroll state in suitable scenarios. |
| ThemeData |
Centralizes application styling. |
54. Official Flutter Resources
55. Flutter Training Resources
56. Key Takeaways
- Responsive UI allows Flutter applications to work effectively across different screen sizes.
- Responsive design focuses on fitting UI into available space.
- Adaptive design focuses on selecting an appropriate and usable interface for that space.
- Use
MediaQuery.sizeOf for application-window measurements.
- Use
LayoutBuilder for component-level responsive behavior.
- Use breakpoints based on layout requirements rather than physical device names.
- Use
Expanded, Flexible, and Wrap to avoid unnecessary fixed dimensions.
- Use responsive grids instead of hard-coded device-specific column counts.
- Use maximum widths to prevent large-screen content from becoming excessively wide.
- Use
SafeArea to protect important content from system UI and display cutouts.
- Support touch, mouse, trackpad, and keyboard interaction.
- Preserve application state when the window or device configuration changes.
- Break complex interfaces into smaller reusable widgets.
- Test the application at different widths, orientations, platforms, and input methods.
Responsive UI in Flutter is ultimately about building flexible interfaces that respond to the space and capabilities available at runtime. By combining MediaQuery, LayoutBuilder, flexible widgets, meaningful breakpoints, adaptive navigation, reusable components, and careful testing, developers can create Flutter applications that remain usable across mobile, tablet, desktop, web, and other form factors. :contentReference[oaicite:24]{index=24}